﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using ICSharpCode.SharpZipLib.Zip;

namespace Mefisto4.Core.Zip
{
    public class ZipHelper
    {
        #region Methods

        /// <summary>
        /// Méthode permettant de zipper un dossier
        /// </summary>
        /// <param name="target">Fichier zip final</param>
        /// <param name="sourcePath">Dossier à zipper</param>
        /// <param name="pwd">Mot de passe à appliquer (chaîne vide si nul)</param>
        /// <param name="excludeFromCompression">Fonction permettant d'exclure des fichiers de la compression</param>
        public static void Zip(string target, string sourcePath, string pwd, Func<string, bool> excludeFromCompression)
        {
            using (var stream = File.Create(target))
            {
                Unzip(stream, sourcePath, pwd, excludeFromCompression);
            }
        }

        /// <summary>
        /// Méthode permettant de dézipper une archive
        /// </summary>
        /// <param name="source">Fichier zip à dézipper</param>
        /// <param name="targetPath">Dossier dans lequel déposer le contenu de l'archive</param>
        /// <param name="pwd">Mot de passe permettant de dézipper l'archive (chaîne vide si nul)</param>
        /// <param name="excludeFromDecompression">Fonction permettant d'exclure des fichiers de la décompression</param>
        public static void Unzip(string source, string targetPath, string pwd, Func<string, bool> excludeFromDecompression)
        {
            if (!File.Exists(source))
                return;

            using (var stream = File.OpenRead(source))
            {
                Unzip(stream, targetPath, pwd, excludeFromDecompression);
            }
        }

        #endregion

        #region Internal Methods

        public static void Zip(Stream target, string sourcePath, Func<string, bool> excludeFromCompression)
        {
            sourcePath = Path.GetFullPath(sourcePath);

            string parentDirectory = Path.GetDirectoryName(sourcePath);

            int trimOffset = (string.IsNullOrEmpty(parentDirectory)
                                  ? Path.GetPathRoot(sourcePath).Length
                                  : parentDirectory.Length);


            List<string> fileSystemEntries = new List<string>();

            fileSystemEntries.AddRange(Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories)
                              .Select(d => d + "\\"));

            fileSystemEntries.AddRange(Directory.GetFiles(sourcePath, "*", SearchOption.AllDirectories));


            using (ZipOutputStream compressor = new ZipOutputStream(target))
            {
                compressor.SetLevel(9);

                foreach (string filePath in fileSystemEntries)
                {
                    if (excludeFromCompression(filePath))
                    {
                        continue;
                    }

                    compressor.PutNextEntry(new ZipEntry(filePath.Substring(trimOffset)));

                    if (filePath.EndsWith(@"\"))
                    {
                        continue;
                    }

                    byte[] data = new byte[2048];

                    using (FileStream input = File.OpenRead(filePath))
                    {
                        int bytesRead;

                        while ((bytesRead = input.Read(data, 0, data.Length)) > 0)
                        {
                            compressor.Write(data, 0, bytesRead);
                        }
                    }
                }

                compressor.Finish();
            }
        }

        public static void Unzip(Stream source, string targetPath, string pwd, Func<string, bool> excludeFromDecompression)
        {
            targetPath = Path.GetFullPath(targetPath);

            using (var decompressor = new ZipInputStream(source))
            {
                if (!string.IsNullOrEmpty(pwd))
                    decompressor.Password = pwd;

                ZipEntry entry;

                while ((entry = decompressor.GetNextEntry()) != null)
                {
                    if (excludeFromDecompression(entry.Name))
                        continue;

                    var filePath = Path.Combine(targetPath, entry.Name);

                    var directoryPath = Path.GetDirectoryName(filePath);

                    if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
                        Directory.CreateDirectory(directoryPath);

                    if (entry.IsDirectory)
                        continue;

                    var data = new byte[2048];
                    using (var streamWriter = File.Create(filePath))
                    {
                        int bytesRead;
                        while ((bytesRead = decompressor.Read(data, 0, data.Length)) > 0)
                        {
                            streamWriter.Write(data, 0, bytesRead);
                        }
                    }
                }
            }
        }

        #endregion
    }
}